package com.ly.fn.biz.ins.srv.api.util;

import com.ly.fn.biz.ins.common.term.annotation.Func;
import com.ly.fn.biz.ins.srv.api.common.ServiceConfig;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.SneakyThrows;
import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @Author xiaoyuan.wang
 * @Date 2021/9/27 10:46
 * @Version 1.0
 */
public class JavassistUtil {

    public JavassistUtil() {

    }

    public static Class toControllerClass(String packageName, Class interfaceClz, String qualifierName) throws CannotCompileException, NotFoundException {
        Method[] methods = interfaceClz.getMethods();
        String controllerName = packageName + "." + getClassName(interfaceClz, qualifierName) + "Controller";
        ClassPool pool = new ClassPool();
        pool.appendClassPath(new LoaderClassPath(interfaceClz.getClassLoader()));

        // 创建类
        CtClass cc = pool.makeClass(controllerName);
        ClassFile ccFile = cc.getClassFile();
        ConstPool constPool = ccFile.getConstPool();
        // 添加类注解
        AnnotationsAttribute bodyAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation bodyAnnot = new Annotation(RestController.class.getName(), constPool);
        bodyAttr.addAnnotation(bodyAnnot);
        RequestMapping nsRequestMapping = AnnotationUtils.getAnnotation(interfaceClz, RequestMapping.class);
        if (nsRequestMapping != null) {
            if (nsRequestMapping.value() != null && nsRequestMapping.value().length > 0) {
                Annotation bodyAnnot2 = new Annotation(RequestMapping.class.getName(), constPool);
                ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
                StringMemberValue[] array = new StringMemberValue[nsRequestMapping.value().length];
                for (int i = 0; i < nsRequestMapping.value().length; i++) {
                    StringMemberValue memberValue = new StringMemberValue(nsRequestMapping.value()[i], constPool);
                    array[i] = memberValue;
                }
                arrayMemberValue.setValue(array);
                bodyAnnot2.addMemberValue("value", arrayMemberValue);
                bodyAttr.addAnnotation(bodyAnnot2);
            }
        }
        ccFile.addAttribute(bodyAttr);
        //添加字段
        CtClass fieldCtClass = pool.getCtClass(interfaceClz.getName());
        CtField ctField = new CtField(fieldCtClass, getFieldName(interfaceClz, null), cc);
        AnnotationsAttribute fieldAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation fieldAnnot = new Annotation(Autowired.class.getName(), constPool);
        fieldAttr.addAnnotation(fieldAnnot);
        //解决一个接口多个实现问题
        if (!StringUtils.isEmpty(qualifierName)) {
            Annotation qualifierAnnot = new Annotation(Qualifier.class.getName(), constPool);
            StringMemberValue memberValue = new StringMemberValue(qualifierName, constPool);
            qualifierAnnot.addMemberValue("value", memberValue);
            fieldAttr.addAnnotation(qualifierAnnot);
        }

        ctField.getFieldInfo().addAttribute(fieldAttr);
        cc.addField(ctField);

        Map<String, List<Method>> methodMap = new HashMap<>();
        for (Method method : methods) {
            List<Method> methodList = methodMap.get(method.getName());
            if (methodList == null) {
                methodList = Lists.newArrayList();
            }
            methodList.add(method);
            methodMap.put(method.getName(), methodList);
        }
        for (Map.Entry<String, List<Method>> entry : methodMap.entrySet()) {
            List<Method> methodList = entry.getValue();
            boolean hasMulit = methodList.size() > 1 ? true : false;
            int index = 1;
            for (Method method : methodList) {
                //返回类型
                CtClass returnCtClass = pool.getCtClass(method.getReturnType().getName());
                //参数类型
                CtClass[] paraCtClasses = new CtClass[method.getParameterTypes().length];
                StringBuilder parametersStringBuilder = new StringBuilder();
                for (int i = 0; i < method.getParameterTypes().length; i++) {
                    CtClass paraCtClass = pool.getCtClass(method.getParameterTypes()[i].getName());
                    paraCtClasses[i] = paraCtClass;
                    if (i < method.getParameterTypes().length - 1) {
                        parametersStringBuilder.append("$" + (i + 1) + ",");
                    } else {
                        parametersStringBuilder.append("$" + (i + 1));
                    }
                }
                CtMethod ctMethod = new CtMethod(returnCtClass, method.getName() + (hasMulit ? "_" + index : ""), paraCtClasses, cc);
                ctMethod.setModifiers(Modifier.PUBLIC);
                StringBuffer body = new StringBuffer();
                body.append("{");
                body.append("\n    return " + ctField.getName() + "." + method.getName() + "(" + parametersStringBuilder.toString() + ");");
                body.append("\n}");
                ctMethod.setBody(body.toString());
                cc.addMethod(ctMethod);

                // 添加方法注解
                AnnotationsAttribute methodAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation postAnnot = new Annotation(PostMapping.class.getName(), constPool);
                RequestMapping requestMappingMethod = AnnotationUtils.getAnnotation(method, RequestMapping.class);
                if (requestMappingMethod != null) {
                    if (requestMappingMethod.value() != null && requestMappingMethod.value().length > 0) {
                        ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
                        StringMemberValue[] array = new StringMemberValue[requestMappingMethod.value().length];
                        for (int i = 0; i < requestMappingMethod.value().length; i++) {
                            StringMemberValue memberValue = new StringMemberValue(requestMappingMethod.value()[i], constPool);
                            array[i] = memberValue;
                        }
                        arrayMemberValue.setValue(array);
                        postAnnot.addMemberValue("value", arrayMemberValue);
                        methodAttr.addAnnotation(postAnnot);
                    }
                } else {
                    ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
                    StringMemberValue[] array = new StringMemberValue[1];
                    if (nsRequestMapping != null && nsRequestMapping.value() != null && nsRequestMapping.value().length > 0) {
                        array[0] = new StringMemberValue("/" + method.getName() + (hasMulit ? "_" + index : ""), constPool);
                    } else {
                        array[0] = new StringMemberValue("/" + (StringUtils.isEmpty(qualifierName) ? interfaceClz.getSimpleName().toLowerCase() : qualifierName) + "/" + method.getName() + (hasMulit ? "_" + index : ""), constPool);
                    }
                    arrayMemberValue.setValue(array);
                    postAnnot.addMemberValue("value", arrayMemberValue);
                    methodAttr.addAnnotation(postAnnot);
                }

                //SneakyThrows
                Annotation sneakyAnnot = new Annotation(SneakyThrows.class.getName(), constPool);
                methodAttr.addAnnotation(sneakyAnnot);

                ctMethod.getMethodInfo().addAttribute(methodAttr);

                int parameterSize = method.getParameterTypes().length;
                if (parameterSize > 0) {
                    // 添加参数注解
                    ParameterAnnotationsAttribute parameterAtrribute = new ParameterAnnotationsAttribute(
                            constPool, ParameterAnnotationsAttribute.visibleTag);
                    Annotation paramBodyAnnot = new Annotation(RequestBody.class.getName(), constPool);

                    Annotation[][] paramArrays = new Annotation[parameterSize][1];
                    int arryIndex = 0;
                    for (int i = 0; i < parameterSize; i++) {
                        if (i == 0) {
                            paramArrays[0][0] = paramBodyAnnot;
                        }else {
                            Annotation paramAnnot = new Annotation(RequestBody.class.getName(), constPool);
                            BooleanMemberValue booleanMemberValue = new BooleanMemberValue(constPool);
                            booleanMemberValue.setValue(false);
                            paramAnnot.addMemberValue("required", booleanMemberValue);
                            StringMemberValue stringMemberValue = new StringMemberValue(constPool);
                            stringMemberValue.setValue("param" + arryIndex);
                            paramAnnot.addMemberValue("name", stringMemberValue);
                            paramArrays[arryIndex][0] = paramAnnot;
                        }
                        arryIndex++;
                    }
                    parameterAtrribute.setAnnotations(paramArrays);
                    ctMethod.getMethodInfo().addAttribute(parameterAtrribute);
                }

                index++;
            }

        }

        String exportPath = ServiceConfig.instance.getExportClzPath();
        if(!StringUtils.isEmpty(exportPath)) {
            exportToDirectory(cc, exportPath);
        }
        return cc.toClass();
    }


    public static Class termToControllerClass(String packageName,Class termClz) throws CannotCompileException, NotFoundException {
        Method[] methods = termClz.getMethods();
        String controllerName = packageName + "." + termClz.getSimpleName()+ "Controller";
        ClassPool pool = new ClassPool();
        pool.appendClassPath(new LoaderClassPath(termClz.getClassLoader()));

        // 创建类
        CtClass cc = pool.makeClass(controllerName);
        ClassFile ccFile = cc.getClassFile();
        ConstPool constPool = ccFile.getConstPool();
        // 添加类注解
        AnnotationsAttribute bodyAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation bodyAnnot = new Annotation(RestController.class.getName(), constPool);
        bodyAttr.addAnnotation(bodyAnnot);
        RequestMapping nsRequestMapping = AnnotationUtils.getAnnotation(termClz, RequestMapping.class);
        if (nsRequestMapping != null) {
            if (nsRequestMapping.value() != null && nsRequestMapping.value().length > 0) {
                Annotation bodyAnnot2 = new Annotation(RequestMapping.class.getName(), constPool);
                ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
                StringMemberValue[] array = new StringMemberValue[nsRequestMapping.value().length];
                for (int i = 0; i < nsRequestMapping.value().length; i++) {
                    StringMemberValue memberValue = new StringMemberValue(nsRequestMapping.value()[i], constPool);
                    array[i] = memberValue;
                }
                arrayMemberValue.setValue(array);
                bodyAnnot2.addMemberValue("value", arrayMemberValue);
                bodyAttr.addAnnotation(bodyAnnot2);
            }
        }
        ccFile.addAttribute(bodyAttr);
        //添加字段
        CtClass fieldCtClass = pool.getCtClass(termClz.getName());
        CtField ctField = new CtField(fieldCtClass, getFieldName(termClz, null), cc);
        AnnotationsAttribute fieldAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation fieldAnnot = new Annotation(Autowired.class.getName(), constPool);
        fieldAttr.addAnnotation(fieldAnnot);

        ctField.getFieldInfo().addAttribute(fieldAttr);
        cc.addField(ctField);

        for (Method method : methods) {
            Func func = method.getAnnotation(Func.class);
            if (func != null) {
                //返回类型
                CtClass returnCtClass = pool.getCtClass(method.getReturnType().getName());
                //参数类型
                CtClass[] paraCtClasses = new CtClass[method.getParameterTypes().length];
                StringBuilder parametersStringBuilder = new StringBuilder();
                for (int i = 0; i < method.getParameterTypes().length; i++) {
                    CtClass paraCtClass = pool.getCtClass(method.getParameterTypes()[i].getName());
                    paraCtClasses[i] = paraCtClass;
                    if (i < method.getParameterTypes().length - 1) {
                        parametersStringBuilder.append("$" + (i + 1) + ",");
                    } else {
                        parametersStringBuilder.append("$" + (i + 1));
                    }
                }
                CtMethod ctMethod = new CtMethod(returnCtClass, method.getName(), paraCtClasses, cc);
                ctMethod.setModifiers(Modifier.PUBLIC);
                StringBuffer body = new StringBuffer();
                body.append("{");
                body.append("\n    return " + ctField.getName() + "." + method.getName() + "(" + parametersStringBuilder + ");");
                body.append("\n}");
                ctMethod.setBody(body.toString());
                cc.addMethod(ctMethod);

                // 添加方法注解
                AnnotationsAttribute methodAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation postAnnot = new Annotation(PostMapping.class.getName(), constPool);
                RequestMapping requestMappingMethod = AnnotationUtils.getAnnotation(method, RequestMapping.class);
                if (requestMappingMethod != null) {
                    if (requestMappingMethod.value() != null && requestMappingMethod.value().length > 0) {
                        ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
                        StringMemberValue[] array = new StringMemberValue[requestMappingMethod.value().length];
                        for (int i = 0; i < requestMappingMethod.value().length; i++) {
                            StringMemberValue memberValue = new StringMemberValue(requestMappingMethod.value()[i], constPool);
                            array[i] = memberValue;
                        }
                        arrayMemberValue.setValue(array);
                        postAnnot.addMemberValue("value", arrayMemberValue);
                        methodAttr.addAnnotation(postAnnot);
                    }
                } else {
                    ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
                    StringMemberValue[] array = new StringMemberValue[1];
                    array[0] = new StringMemberValue("/" + func.funcCode(), constPool);
                    arrayMemberValue.setValue(array);
                    postAnnot.addMemberValue("value", arrayMemberValue);
                    methodAttr.addAnnotation(postAnnot);
                }

                //SneakyThrows
                Annotation sneakyAnnot = new Annotation(SneakyThrows.class.getName(), constPool);
                methodAttr.addAnnotation(sneakyAnnot);
                ctMethod.getMethodInfo().addAttribute(methodAttr);

                int parameterSize = method.getParameterTypes().length;
                if (parameterSize > 0) {
                    // 添加参数注解
                    ParameterAnnotationsAttribute parameterAtrribute = new ParameterAnnotationsAttribute(
                            constPool, ParameterAnnotationsAttribute.visibleTag);
                    Annotation paramBodyAnnot = new Annotation(RequestBody.class.getName(), constPool);


                    Annotation[][] paramArrays = new Annotation[parameterSize][1];
                    int arryIndex = 0;
                    for (int i = 0; i < parameterSize; i++) {
                        if (i == 0) {
                            paramArrays[0][0] = paramBodyAnnot;
                        }
                        else {
                            Annotation paramAnnot = new Annotation(ModelAttribute.class.getName(), constPool);
                            BooleanMemberValue booleanMemberValue = new BooleanMemberValue(constPool);
                            booleanMemberValue.setValue(false);
                            paramAnnot.addMemberValue("required", booleanMemberValue);
                            StringMemberValue stringMemberValue = new StringMemberValue(constPool);
                            stringMemberValue.setValue("param" + arryIndex);
                            paramAnnot.addMemberValue("name", stringMemberValue);
                            paramArrays[arryIndex][0] = paramAnnot;
                        }
                        arryIndex++;
                    }
                    parameterAtrribute.setAnnotations(paramArrays);
                    ctMethod.getMethodInfo().addAttribute(parameterAtrribute);
                }
            }
        }

        String exportPath = ServiceConfig.instance.getExportClzPath();
        if(!StringUtils.isEmpty(exportPath)) {
            exportToDirectory(cc, exportPath);
        }
        return cc.toClass();
    }
    private static String getClassName(Class interfaceClz, String qualifierName) {
        String name = StringUtils.isEmpty(qualifierName) ? interfaceClz.getSimpleName() : qualifierName;
        if (name.endsWith("Service")) {
            name = name.substring(0, name.lastIndexOf("Service"));
        }
        if (name.startsWith("I")) {
            return name.substring(1, name.length());
        } else {
            return name;
        }
    }

    private static String getFieldName(Class interfaceClz, String qualifierName) {
        String name = getClassName(interfaceClz, qualifierName);
        StringBuilder stringBuilder = new StringBuilder();
        char[] chars = name.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if (i == 0) {
                stringBuilder.append(String.valueOf(chars[i]).toLowerCase());
            } else {
                stringBuilder.append(String.valueOf(chars[i]));
            }
        }
        stringBuilder.append("FacadeService");
        return stringBuilder.toString();
    }

    private static void exportToDirectory(CtClass ctClass, String directory) {

        try {
            File file = new File(directory);
            if (!file.exists()) {
                file.mkdirs();
            }
            // 获取类的字节数组
            byte[] bytecode = ctClass.toBytecode();

            // 将字节数组写入本地文件
            String filename = ctClass.getSimpleName().replace(".", "/") + ".class";

            FileOutputStream fos = new FileOutputStream(directory + "/" + filename);

            fos.write(bytecode);

            fos.close();
        } catch (Exception ex) {

        }
    }
}
